#!/usr/bin/env python3
"""
Zos IP 服务器端 - 完整修复版
支持客户端扫描、远程命令执行、批量操作等功能
修复批量修改计算机名只发送到一台机器的问题
"""

import tkinter as tk
from tkinter import ttk, messagebox, filedialog, scrolledtext, simpledialog
import threading
import socket
import json
import time
import os
import subprocess
from datetime import datetime
import concurrent.futures
import ipaddress
import logging
import base64
import re

# 添加导入
try:
    from hostname_manager import HostnameManager
except ImportError:
    # 如果导入失败，创建一个简单的占位类
    class HostnameManager:
        def __init__(self, logger=None):
            self.logger = logger
        
        def batch_update_hostnames(self, clients_config, port, progress_callback):
            # 简单的回退实现
            success_count = 0
            detailed_results = []
            for i, client in enumerate(clients_config):
                progress_callback(i + 1, len(clients_config), success_count, 
                                f"处理 {client['current_hostname']}...")
                # 这里可以调用原有的发送命令逻辑
                result = {
                    'ip': client['ip'],
                    'current_hostname': client['current_hostname'],
                    'new_hostname': client['new_hostname'],
                    'success': True,
                    'message': '修改成功'
                }
                detailed_results.append(result)
                success_count += 1
                progress_callback(i + 1, len(clients_config), success_count, 
                                f"完成 {client['current_hostname']}")
            return success_count, len(clients_config) - success_count, detailed_results

class FogIPServer:
    def __init__(self):
        self.root = tk.Tk()
        self.root.title("Zos IP 服务器端 - 网络客户端管理系统")
        self.root.geometry("1200x800")
        self.set_icon()
        
        # 客户端数据存储
        self.clients = {}
        self.selected_clients = []
        
        # 服务器配置
        self.server_config = {
            'scan_subnets': ['192.168.1.0/24', '192.168.0.0/24'],
            'scan_timeout': 3,
            'client_port': 8888,
            'max_threads': 50
        }
        
        # 扫描控制
        self.scanning = False
        self.scan_thread = None
        self.scan_executor = None  # 线程池执行器
        
        # 唤醒分组数据
        self.wake_groups = {}
        self.current_group = None
        
        # 设置日志
        self.setup_logging()
        
        self.setup_ui()
        self.load_config()
        self.load_wake_groups()
        
    def setup_logging(self):
        """设置日志系统"""
        logging.basicConfig(
            level=logging.DEBUG,
            format='%(asctime)s - %(levelname)s - %(message)s',
            handlers=[
                logging.FileHandler('server_debug.log', encoding='utf-8'),
                logging.StreamHandler()
            ]
        )
        self.logger = logging.getLogger(__name__)
        
    def set_icon(self):
        """设置窗口图标"""
        try:
            self.root.iconbitmap("server_icon.ico")
        except:
            pass

    def setup_ui(self):
        """设置用户界面"""
        # 创建主框架
        main_frame = ttk.Frame(self.root, padding="10")
        main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
        
        # 配置网格权重
        self.root.columnconfigure(0, weight=1)
        self.root.rowconfigure(0, weight=1)
        main_frame.columnconfigure(1, weight=1)
        main_frame.rowconfigure(1, weight=1)
        
        # 顶部控制区域
        self.setup_control_area(main_frame)
        
        # 客户端列表区域
        self.setup_client_list_area(main_frame)
        
        # 操作按钮区域
        self.setup_action_buttons(main_frame)
        
        # 日志区域
        self.setup_log_area(main_frame)
        
    def setup_control_area(self, parent):
        """设置控制区域"""
        control_frame = ttk.LabelFrame(parent, text="扫描控制", padding="10")
        control_frame.grid(row=0, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(0, 10))
        control_frame.columnconfigure(1, weight=1)
        
        # 第一行：网段配置
        subnet_frame = ttk.Frame(control_frame)
        subnet_frame.grid(row=0, column=0, columnspan=4, sticky=(tk.W, tk.E), pady=(0, 10))
        
        ttk.Label(subnet_frame, text="扫描网段:").grid(row=0, column=0, sticky=tk.W)
        self.subnet_var = tk.StringVar(value=", ".join(self.server_config['scan_subnets']))
        subnet_entry = ttk.Entry(subnet_frame, textvariable=self.subnet_var, width=50)
        subnet_entry.grid(row=0, column=1, padx=(5, 15), sticky=(tk.W, tk.E))
        
        ttk.Label(subnet_frame, text="超时(秒):").grid(row=0, column=2, sticky=tk.W)
        self.timeout_var = tk.StringVar(value=str(self.server_config['scan_timeout']))
        timeout_entry = ttk.Entry(subnet_frame, textvariable=self.timeout_var, width=8)
        timeout_entry.grid(row=0, column=3, padx=(5, 15), sticky=tk.W)
        
        ttk.Label(subnet_frame, text="线程数:").grid(row=0, column=4, sticky=tk.W)
        self.threads_var = tk.StringVar(value=str(self.server_config['max_threads']))
        threads_entry = ttk.Entry(subnet_frame, textvariable=self.threads_var, width=8)
        threads_entry.grid(row=0, column=5, padx=(5, 15), sticky=tk.W)
        
        # 第二行：按钮
        button_frame = ttk.Frame(control_frame)
        button_frame.grid(row=1, column=0, columnspan=4, sticky=(tk.W, tk.E))
        
        ttk.Button(button_frame, text="开始扫描", 
                  command=self.start_scan).pack(side=tk.LEFT, padx=(0, 5))
        ttk.Button(button_frame, text="停止扫描", 
                  command=self.stop_scan).pack(side=tk.LEFT, padx=5)
        ttk.Button(button_frame, text="快速扫描", 
                  command=self.quick_scan).pack(side=tk.LEFT, padx=5)
        ttk.Button(button_frame, text="广播发现", 
                  command=self.broadcast_discovery).pack(side=tk.LEFT, padx=5)
        
        # 状态和进度
        self.status_var = tk.StringVar(value="就绪")
        status_label = ttk.Label(button_frame, textvariable=self.status_var)
        status_label.pack(side=tk.LEFT, padx=(20, 0))
        
        self.progress_var = tk.DoubleVar()
        self.progress = ttk.Progressbar(button_frame, variable=self.progress_var, maximum=100)
        self.progress.pack(side=tk.RIGHT, fill=tk.X, expand=True, padx=(20, 0))
        
        # 调试选项
        debug_frame = ttk.Frame(control_frame)
        debug_frame.grid(row=2, column=0, columnspan=4, sticky=(tk.W, tk.E), pady=(5, 0))
        
        self.debug_var = tk.BooleanVar(value=True)
        debug_cb = ttk.Checkbutton(debug_frame, text="启用调试日志", variable=self.debug_var)
        debug_cb.pack(side=tk.LEFT, padx=(0, 10))
        
        ttk.Button(debug_frame, text="测试单个IP", 
                  command=self.test_single_ip).pack(side=tk.LEFT, padx=5)
        
        self.test_ip_var = tk.StringVar(value="192.168.1.100")
        test_ip_entry = ttk.Entry(debug_frame, textvariable=self.test_ip_var, width=15)
        test_ip_entry.pack(side=tk.LEFT, padx=5)
        
    def setup_client_list_area(self, parent):
        """设置客户端列表区域"""
        list_frame = ttk.LabelFrame(parent, text="在线客户端", padding="10")
        list_frame.grid(row=1, column=0, columnspan=2, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(0, 10))
        list_frame.columnconfigure(0, weight=1)
        list_frame.rowconfigure(1, weight=1)  # 让树形视图区域可以扩展
        
        # 选择按钮区域 - 增加高度
        selection_frame = ttk.Frame(list_frame)
        selection_frame.grid(row=0, column=0, sticky=(tk.W, tk.E), pady=(0, 10))  # 增加底部间距
        
        # 设置按钮高度
        button_style = ttk.Style()
        button_style.configure('Tall.TButton', padding=(10, 8))  # 增加内边距
        
        ttk.Button(selection_frame, text="全选", 
                  command=self.select_all_clients,
                  style='Tall.TButton').pack(side=tk.LEFT, padx=(0, 5))
        ttk.Button(selection_frame, text="取消全选", 
                  command=self.deselect_all_clients,
                  style='Tall.TButton').pack(side=tk.LEFT, padx=5)
        ttk.Button(selection_frame, text="反选", 
                  command=self.invert_selection,
                  style='Tall.TButton').pack(side=tk.LEFT, padx=5)
        
        # 添加选择状态显示
        self.selection_count_var = tk.StringVar(value="未选择任何客户端")
        selection_label = ttk.Label(selection_frame, textvariable=self.selection_count_var)
        selection_label.pack(side=tk.RIGHT, padx=(0, 10))
        
        # 创建树形视图
        columns = ('select', 'hostname', 'ip', 'mac', 'status', 'last_seen', 'client_version')
        self.client_tree = ttk.Treeview(list_frame, columns=columns, show='headings', selectmode='extended')
        
        # 定义列
        self.client_tree.heading('select', text='选择', command=lambda: self.sort_treeview('select', False))
        self.client_tree.heading('hostname', text='计算机名', command=lambda: self.sort_treeview('hostname', False))
        self.client_tree.heading('ip', text='IP地址', command=lambda: self.sort_treeview('ip', False))
        self.client_tree.heading('mac', text='MAC地址', command=lambda: self.sort_treeview('mac', False))
        self.client_tree.heading('status', text='状态', command=lambda: self.sort_treeview('status', False))
        self.client_tree.heading('last_seen', text='最后在线', command=lambda: self.sort_treeview('last_seen', False))
        self.client_tree.heading('client_version', text='客户端版本', command=lambda: self.sort_treeview('client_version', False))
        
        # 设置列宽
        self.client_tree.column('select', width=50, anchor='center')
        self.client_tree.column('hostname', width=150)
        self.client_tree.column('ip', width=120)
        self.client_tree.column('mac', width=150)
        self.client_tree.column('status', width=80, anchor='center')
        self.client_tree.column('last_seen', width=150)
        self.client_tree.column('client_version', width=100)
        
        # 添加滚动条
        scrollbar = ttk.Scrollbar(list_frame, orient=tk.VERTICAL, command=self.client_tree.yview)
        self.client_tree.configure(yscrollcommand=scrollbar.set)
        
        # 布局
        self.client_tree.grid(row=1, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
        scrollbar.grid(row=1, column=1, sticky=(tk.N, tk.S))
        
        # 绑定事件
        self.client_tree.bind('<Double-1>', self.on_client_double_click)
        self.client_tree.bind('<<TreeviewSelect>>', self.on_client_select)
        self.client_tree.bind('<Button-1>', self.on_treeview_click)
        
        # 右键菜单
        self.setup_context_menu()
        
    def on_treeview_click(self, event):
        """处理树形视图点击事件 - 用于选择列"""
        item = self.client_tree.identify_row(event.y)
        column = self.client_tree.identify_column(event.x)
        
        if item and column == '#1':  # 选择列
            # 切换选择状态
            current_value = self.client_tree.set(item, 'select')
            new_value = '✓' if current_value != '✓' else ''
            self.client_tree.set(item, 'select', new_value)
            return 'break'  # 阻止默认选择行为
        
    def select_all_clients(self):
        """全选客户端"""
        for item in self.client_tree.get_children():
            self.client_tree.set(item, 'select', '✓')
        self.client_tree.selection_set(self.client_tree.get_children())
        
    def deselect_all_clients(self):
        """取消全选客户端"""
        for item in self.client_tree.get_children():
            self.client_tree.set(item, 'select', '')
        self.client_tree.selection_remove(self.client_tree.get_children())
        
    def invert_selection(self):
        """反选客户端"""
        for item in self.client_tree.get_children():
            current_value = self.client_tree.set(item, 'select')
            self.client_tree.set(item, 'select', '✓' if current_value != '✓' else '')
        
        all_items = set(self.client_tree.get_children())
        selected_items = set(self.client_tree.selection())
        new_selection = all_items - selected_items
        self.client_tree.selection_set(new_selection)
        
    def sort_treeview(self, col, reverse):
        """对树形视图进行排序"""
        # 获取所有行数据
        data = [(self.client_tree.set(child, col), child) for child in self.client_tree.get_children('')]
        
        # 根据列类型进行排序
        if col in ['ip']:
            # IP地址特殊排序
            data.sort(key=lambda x: self.ip_to_int(x[0]), reverse=reverse)
        elif col in ['select']:
            # 选择列特殊排序（✓在前）
            data.sort(key=lambda x: (x[0] != '✓', x[0]), reverse=reverse)
        else:
            # 其他列按字符串排序
            data.sort(reverse=reverse)
        
        # 重新排列行
        for index, (_, child) in enumerate(data):
            self.client_tree.move(child, '', index)
        
        # 设置下一次排序为相反顺序
        self.client_tree.heading(col, command=lambda: self.sort_treeview(col, not reverse))
    
    def ip_to_int(self, ip):
        """将IP地址转换为整数用于排序"""
        try:
            parts = ip.split('.')
            return int(parts[0]) * 256**3 + int(parts[1]) * 256**2 + int(parts[2]) * 256 + int(parts[3])
        except:
            return 0
        
    def setup_context_menu(self):
        """设置右键菜单"""
        self.context_menu = tk.Menu(self.client_tree, tearoff=0)
        self.context_menu.add_command(label="刷新状态", command=self.refresh_selected_status)
        self.context_menu.add_command(label="修改IP", command=self.batch_modify_ip)
        self.context_menu.add_command(label="修改计算机名", command=self.batch_modify_hostname)
        self.context_menu.add_command(label="远程重启", command=self.remote_reboot)
        self.context_menu.add_command(label="远程关机", command=self.remote_shutdown)
        self.context_menu.add_separator()
        self.context_menu.add_command(label="查看详情", command=self.show_selected_details)
        self.context_menu.add_command(label="分发文件", command=self.distribute_files)
        
        self.client_tree.bind("<Button-3>", self.show_context_menu)
        
    def show_context_menu(self, event):
        """显示右键菜单"""
        item = self.client_tree.identify_row(event.y)
        if item:
            self.client_tree.selection_set(item)
            self.context_menu.post(event.x_root, event.y_root)
        
    def setup_action_buttons(self, parent):
        """设置操作按钮区域"""
        action_frame = ttk.LabelFrame(parent, text="批量操作", padding="10")
        action_frame.grid(row=2, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(0, 10))
        
        # 按钮框架
        btn_frame = ttk.Frame(action_frame)
        btn_frame.pack(fill=tk.X)
        
        ttk.Button(btn_frame, text="刷新选中状态", 
                  command=self.refresh_selected_status).pack(side=tk.LEFT, padx=2)
        ttk.Button(btn_frame, text="批量修改IP", 
                  command=self.batch_modify_ip).pack(side=tk.LEFT, padx=2)
        ttk.Button(btn_frame, text="修改计算机名", 
                  command=self.batch_modify_hostname).pack(side=tk.LEFT, padx=2)
        ttk.Button(btn_frame, text="远程重启", 
                  command=self.remote_reboot).pack(side=tk.LEFT, padx=2)
        ttk.Button(btn_frame, text="远程关机", 
                  command=self.remote_shutdown).pack(side=tk.LEFT, padx=2)
        ttk.Button(btn_frame, text="分发文件", 
                  command=self.distribute_files).pack(side=tk.LEFT, padx=2)
        ttk.Button(btn_frame, text="执行命令", 
                  command=self.execute_commands).pack(side=tk.LEFT, padx=2)
        ttk.Button(btn_frame, text="唤醒计算机", 
                  command=self.wake_on_lan).pack(side=tk.LEFT, padx=2)
        
    def setup_log_area(self, parent):
        """设置日志区域"""
        log_frame = ttk.LabelFrame(parent, text="操作日志", padding="10")
        log_frame.grid(row=3, column=0, columnspan=2, sticky=(tk.W, tk.E, tk.N, tk.S))
        log_frame.columnconfigure(0, weight=1)
        log_frame.rowconfigure(0, weight=1)
        
        self.log_text = scrolledtext.ScrolledText(log_frame, width=100, height=15)
        self.log_text.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
        
        # 日志按钮
        log_buttons = ttk.Frame(log_frame)
        log_buttons.grid(row=1, column=0, sticky=tk.E, pady=(5, 0))
        
        ttk.Button(log_buttons, text="清空日志", 
                  command=self.clear_log).pack(side=tk.RIGHT, padx=5)
        ttk.Button(log_buttons, text="保存日志", 
                  command=self.save_log).pack(side=tk.RIGHT, padx=5)
        
    def log_message(self, message, level="INFO"):
        """添加日志消息"""
        timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        log_entry = f"[{timestamp}] [{level}] {message}\n"
        
        self.log_text.insert(tk.END, log_entry)
        self.log_text.see(tk.END)
        self.root.update_idletasks()
        
        # 同时记录到文件日志
        if level == "DEBUG" and not self.debug_var.get():
            return
            
        if level == "INFO":
            self.logger.info(message)
        elif level == "WARNING":
            self.logger.warning(message)
        elif level == "ERROR":
            self.logger.error(message)
        elif level == "DEBUG":
            self.logger.debug(message)
        
    def clear_log(self):
        """清空日志"""
        self.log_text.delete(1.0, tk.END)
        
    def save_log(self):
        """保存日志到文件"""
        filename = filedialog.asksaveasfilename(
            defaultextension=".txt",
            filetypes=[("Text files", "*.txt"), ("All files", "*.*")]
        )
        if filename:
            try:
                with open(filename, 'w', encoding='utf-8') as f:
                    f.write(self.log_text.get(1.0, tk.END))
                self.log_message(f"日志已保存到: {filename}")
            except Exception as e:
                messagebox.showerror("错误", f"保存日志失败: {str(e)}")
                
    def load_config(self):
        """加载配置"""
        try:
            if os.path.exists('server_config.json'):
                with open('server_config.json', 'r', encoding='utf-8') as f:
                    config = json.load(f)
                    self.server_config.update(config)
                    self.subnet_var.set(", ".join(self.server_config['scan_subnets']))
                    self.timeout_var.set(str(self.server_config['scan_timeout']))
                    self.threads_var.set(str(self.server_config['max_threads']))
        except Exception as e:
            self.log_message(f"加载配置失败: {str(e)}", "ERROR")
            
    def save_config(self):
        """保存配置"""
        try:
            self.server_config['scan_subnets'] = [s.strip() for s in self.subnet_var.get().split(',')]
            self.server_config['scan_timeout'] = float(self.timeout_var.get())
            self.server_config['max_threads'] = int(self.threads_var.get())
            
            with open('server_config.json', 'w', encoding='utf-8') as f:
                json.dump(self.server_config, f, indent=4, ensure_ascii=False)
        except Exception as e:
            self.log_message(f"保存配置失败: {str(e)}", "ERROR")

    def load_wake_groups(self):
        """加载分组数据"""
        try:
            if os.path.exists('wake_groups.json'):
                with open('wake_groups.json', 'r', encoding='utf-8') as f:
                    self.wake_groups = json.load(f)
        except Exception as e:
            self.log_message(f"加载分组数据失败: {str(e)}", "ERROR")
            self.wake_groups = {}

    def save_wake_groups(self):
        """保存分组数据"""
        try:
            with open('wake_groups.json', 'w', encoding='utf-8') as f:
                json.dump(self.wake_groups, f, indent=4, ensure_ascii=False)
        except Exception as e:
            self.log_message(f"保存分组数据失败: {str(e)}", "ERROR")

    def test_single_ip(self):
        """测试单个IP连接"""
        if self.scanning:
            messagebox.showwarning("警告", "扫描正在进行中，请先停止扫描")
            return
            
        ip = self.test_ip_var.get().strip()
        if not ip:
            messagebox.showwarning("警告", "请输入要测试的IP地址")
            return
            
        def do_test():
            self.log_message(f"开始测试IP: {ip}", "INFO")
            client_info = self.scan_single_ip_enhanced(ip)
            if client_info:
                self.log_message(f"测试成功 - 发现客户端: {client_info}", "INFO")
                self.add_client(client_info)
            else:
                self.log_message(f"测试失败 - 未发现客户端", "WARNING")
    
        threading.Thread(target=do_test, daemon=True).start()

    def start_scan(self):
        """开始扫描网络 - 修复版：扫描前清空列表"""
        if self.scanning:
            messagebox.showwarning("警告", "扫描正在进行中")
            return
            
        self.scanning = True
        self.status_var.set("扫描中...")
        self.progress_var.set(0)
        
        # 清空客户端列表
        self.clients.clear()
        for item in self.client_tree.get_children():
            self.client_tree.delete(item)
        
        # 保存配置
        self.save_config()
        
        # 在新线程中执行扫描
        self.scan_thread = threading.Thread(target=self.scan_network)
        self.scan_thread.daemon = True
        self.scan_thread.start()
        
    def stop_scan(self):
        """停止扫描 - 增强版：真正停止所有扫描活动"""
        if not self.scanning:
            messagebox.showinfo("提示", "当前没有进行中的扫描")
            return
            
        self.scanning = False
        self.status_var.set("正在停止扫描...")
        
        # 停止线程池
        if self.scan_executor:
            self.scan_executor.shutdown(wait=False, cancel_futures=True)
            self.scan_executor = None
            
        self.status_var.set("已停止")
        self.progress_var.set(0)
        self.log_message("扫描已停止", "INFO")
        
    def quick_scan(self):
        """快速扫描 - 修复版：扫描前清空列表"""
        if self.scanning:
            messagebox.showwarning("警告", "扫描正在进行中，请先停止扫描")
            return
            
        # 设置常见网段
        common_subnets = ['192.168.1.0/24', '192.168.0.0/24', '10.0.0.0/24']
        self.subnet_var.set(", ".join(common_subnets))
        
        # 清空客户端列表
        self.clients.clear()
        for item in self.client_tree.get_children():
            self.client_tree.delete(item)
        
        self.start_scan()
        
    def broadcast_discovery(self):
        """广播发现客户端"""
        if self.scanning:
            messagebox.showwarning("警告", "扫描正在进行中，请先停止扫描")
            return
            
        self.log_message("开始广播发现客户端...", "INFO")
        
        # 在新线程中执行广播发现
        broadcast_thread = threading.Thread(target=self.do_broadcast_discovery)
        broadcast_thread.daemon = True
        broadcast_thread.start()
        
    def do_broadcast_discovery(self):
        """执行广播发现"""
        try:
            # 获取所有本地IP地址
            local_ips = self.get_local_ips()
            
            # 对每个网络发送广播
            for ip_info in local_ips:
                if not self.scanning:
                    break
                    
                network = ip_info['network']
                broadcast_addr = ip_info['broadcast']
                
                self.log_message(f"在网络 {network} 发送广播到 {broadcast_addr}", "INFO")
                self.send_broadcast(broadcast_addr)
                
        except Exception as e:
            self.log_message(f"广播发现失败: {str(e)}", "ERROR")
            
    def get_local_ips(self):
        """获取本地IP地址和网络信息"""
        local_ips = []
        try:
            hostname = socket.gethostname()
            addrinfo = socket.getaddrinfo(hostname, None)
            
            for addr in addrinfo:
                if addr[0] == socket.AF_INET:
                    ip = addr[4][0]
                    if not ip.startswith('127.'):
                        # 计算网络地址和广播地址
                        network = '.'.join(ip.split('.')[:3]) + '.0/24'
                        broadcast = '.'.join(ip.split('.')[:3]) + '.255'
                        local_ips.append({
                            'ip': ip,
                            'network': network,
                            'broadcast': broadcast
                        })
        except Exception as e:
            self.log_message(f"获取本地IP失败: {str(e)}", "ERROR")
            
        return local_ips
        
    def send_broadcast(self, broadcast_addr):
        """发送广播包"""
        try:
            sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
            sock.settimeout(2)
            
            # 发送广播消息
            message = json.dumps({
                'type': 'discovery',
                'server': 'fog_ip_server',
                'timestamp': datetime.now().isoformat()
            }).encode('utf-8')
            
            sock.sendto(message, (broadcast_addr, self.server_config['client_port']))
            sock.close()
            
        except Exception as e:
            self.log_message(f"发送广播失败: {str(e)}", "ERROR")
            
    def scan_network(self):
        """扫描网络"""
        subnets = [s.strip() for s in self.subnet_var.get().split(',')]
        total_ips = 0
        scanned_ips = 0
        
        # 计算总IP数量
        for subnet in subnets:
            try:
                network = ipaddress.ip_network(subnet, strict=False)
                total_ips += network.num_addresses - 2  # 排除网络地址和广播地址
            except Exception as e:
                self.log_message(f"无效网段: {subnet} - {str(e)}", "ERROR")
        
        self.log_message(f"开始扫描 {len(subnets)} 个网段，共约 {total_ips} 个IP地址", "INFO")
        
        try:
            for subnet in subnets:
                if not self.scanning:
                    break
                    
                self.log_message(f"扫描网段: {subnet}", "INFO")
                found_count = self.scan_subnet(subnet, total_ips, scanned_ips)
                scanned_ips += found_count
                
        except Exception as e:
            self.log_message(f"扫描出错: {str(e)}", "ERROR")
            
        self.status_var.set(f"扫描完成，找到 {len(self.clients)} 个客户端")
        self.scanning = False
        self.progress_var.set(100)
        
    def scan_subnet(self, subnet, total_ips, base_scanned):
        """扫描指定子网"""
        found_count = 0
        try:
            network = ipaddress.ip_network(subnet, strict=False)
            ips = [str(ip) for ip in network.hosts()]  # 所有主机IP
            
            # 使用线程池并发扫描
            self.scan_executor = concurrent.futures.ThreadPoolExecutor(
                max_workers=int(self.threads_var.get()))
            
            # 提交所有扫描任务
            future_to_ip = {
                self.scan_executor.submit(self.scan_single_ip_enhanced, ip): ip 
                for ip in ips[:254]  # 限制前254个IP避免过多
            }
            
            # 处理完成的任务
            for future in concurrent.futures.as_completed(future_to_ip):
                if not self.scanning:
                    self.scan_executor.shutdown(wait=False, cancel_futures=True)
                    break
                    
                ip = future_to_ip[future]
                try:
                    client_info = future.result(timeout=float(self.timeout_var.get()) + 1)
                    if client_info:
                        found_count += 1
                        self.add_client(client_info)
                except concurrent.futures.TimeoutError:
                    pass
                except Exception as e:
                    self.log_message(f"扫描IP {ip} 异常: {str(e)}", "DEBUG")
                
                # 更新进度
                base_scanned += 1
                progress = (base_scanned / total_ips) * 100 if total_ips > 0 else 0
                self.progress_var.set(progress)
                
        except Exception as e:
            self.log_message(f"扫描子网 {subnet} 失败: {str(e)}", "ERROR")
            
        return found_count
        
    def scan_single_ip_enhanced(self, ip):
        """增强版IP扫描 - 多种方式检测客户端"""
        if not self.scanning:
            return None
            
        self.log_message(f"扫描IP: {ip}", "DEBUG")
        
        # 方法1: 检查标准客户端服务
        client_info = self.check_client_service(ip)
        if client_info:
            return client_info
        
        # 方法2: 发送ping请求
        ping_info = self.check_client_ping(ip)
        if ping_info:
            return ping_info
            
        # 方法3: 简单的端口连接测试
        if self.check_port_connectivity(ip):
            return {
                'ip': ip,
                'hostname': f"PC-{ip.replace('.', '-')}",
                'mac': '未知',
                'status': 'online',
                'last_seen': datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
                'version': '未知',
                'type': 'fog_ip_client'
            }
        
        return None

    def check_client_service(self, ip):
        """检查客户端服务是否运行 - 修复版"""
        if not self.scanning:
            return None
            
        try:
            self.log_message(f"检查客户端服务: {ip}:{self.server_config['client_port']}", "DEBUG")
            
            # 尝试连接客户端的服务端口
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            sock.settimeout(float(self.timeout_var.get()))
            
            result = sock.connect_ex((ip, self.server_config['client_port']))
            self.log_message(f"连接结果: {result}", "DEBUG")
            
            if result == 0:
                # 连接成功，获取客户端信息
                try:
                    # 发送身份验证请求
                    auth_request = json.dumps({
                        'action': 'get_info',
                        'timestamp': datetime.now().isoformat()
                    }).encode('utf-8')
                    
                    sock.send(auth_request)
                    sock.settimeout(5)  # 增加超时时间
                    
                    # 接收响应
                    response_data = b""
                    while True:
                        if not self.scanning:
                            break
                        chunk = sock.recv(1024)
                        if not chunk:
                            break
                        response_data += chunk
                        if len(chunk) < 1024:
                            break
                    
                    if response_data:
                        response = response_data.decode('utf-8', errors='ignore').strip()
                        self.log_message(f"收到客户端响应: {response}", "DEBUG")
                        
                        try:
                            client_info = json.loads(response)
                            
                            # 放宽验证条件 - 只要包含基本信息的都认为是客户端
                            if isinstance(client_info, dict):
                                # 构建客户端信息
                                client_data = {
                                    'ip': ip,
                                    'hostname': client_info.get('hostname', '未知'),
                                    'mac': client_info.get('mac', '未知'),
                                    'status': 'online',
                                    'last_seen': datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
                                    'version': client_info.get('version', '未知'),
                                    'type': 'fog_ip_client'  # 确保有这个字段
                                }
                                
                                # 如果客户端没有返回hostname，使用IP作为主机名
                                if client_data['hostname'] == '未知':
                                    try:
                                        # 尝试通过反向DNS获取主机名
                                        hostname = socket.getfqdn(ip)
                                        if hostname and hostname != ip:
                                            client_data['hostname'] = hostname
                                        else:
                                            client_data['hostname'] = f"PC-{ip.replace('.', '-')}"
                                    except:
                                        client_data['hostname'] = f"PC-{ip.replace('.', '-')}"
                                
                                self.log_message(f"发现Zos IP客户端: {client_data['hostname']} ({ip})", "INFO")
                                sock.close()
                                return client_data
                                
                        except json.JSONDecodeError as e:
                            self.log_message(f"JSON解析失败 from {ip}: {response}", "DEBUG")
                            # 即使JSON解析失败，也记录连接成功的客户端
                            client_data = {
                                'ip': ip,
                                'hostname': f"PC-{ip.replace('.', '-')}",
                                'mac': '未知',
                                'status': 'online',
                                'last_seen': datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
                                'version': '未知',
                                'type': 'fog_ip_client'
                            }
                            sock.close()
                            return client_data
                            
                except (socket.timeout, ConnectionResetError, BrokenPipeError) as e:
                    self.log_message(f"与客户端 {ip} 通信超时或中断: {str(e)}", "DEBUG")
                except Exception as e:
                    self.log_message(f"处理客户端 {ip} 响应异常: {str(e)}", "DEBUG")
                    
            sock.close()
            
        except Exception as e:
            self.log_message(f"检查客户端服务 {ip} 异常: {str(e)}", "DEBUG")
            
        return None

    def check_client_ping(self, ip):
        """通过ping命令检查客户端"""
        if not self.scanning:
            return None
            
        try:
            self.log_message(f"发送ping请求到: {ip}", "DEBUG")
            
            # 发送ping格式的请求
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            sock.settimeout(3)
            
            result = sock.connect_ex((ip, self.server_config['client_port']))
            if result == 0:
                # 发送ping请求
                ping_request = json.dumps({
                    'action': 'ping',
                    'timestamp': datetime.now().isoformat()
                }).encode('utf-8')
                
                sock.send(ping_request)
                sock.settimeout(3)
                
                try:
                    response_data = sock.recv(1024)
                    if response_data:
                        response = json.loads(response_data.decode('utf-8'))
                        if response.get('status') == 'success':
                            return {
                                'ip': ip,
                                'hostname': response.get('data', {}).get('hostname', f"PC-{ip.replace('.', '-')}"),
                                'mac': response.get('data', {}).get('mac', '未知'),
                                'status': 'online',
                                'last_seen': datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
                                'version': response.get('data', {}).get('version', '未知'),
                                'type': 'fog_ip_client'
                            }
                except Exception as e:
                    self.log_message(f"Ping响应处理失败: {str(e)}", "DEBUG")
                    
            sock.close()
        except Exception as e:
            self.log_message(f"Ping检查失败: {str(e)}", "DEBUG")
            
        return None

    def check_port_connectivity(self, ip):
        """简单的端口连通性测试"""
        if not self.scanning:
            return False
            
        try:
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            sock.settimeout(2)
            result = sock.connect_ex((ip, self.server_config['client_port']))
            sock.close()
            
            if result == 0:
                self.log_message(f"端口 {self.server_config['client_port']} 可连接: {ip}", "DEBUG")
            return result == 0
        except Exception as e:
            self.log_message(f"端口连通性测试失败: {str(e)}", "DEBUG")
            return False

    def add_client(self, client_info):
        """添加客户端到列表"""
        if not self.scanning:
            return
            
        ip = client_info['ip']
        
        if ip not in self.clients:
            self.clients[ip] = client_info
            
            # 在UI线程中更新界面
            self.root.after(0, self._add_client_to_tree, client_info)
            
            self.log_message(f"发现Zos客户端: {client_info['hostname']} ({ip})", "INFO")
            
    def _add_client_to_tree(self, client_info):
        """在UI线程中添加客户端到树形视图"""
        # 检查是否已存在
        for item in self.client_tree.get_children():
            values = self.client_tree.item(item, 'values')
            if values[2] == client_info['ip']:  # IP地址匹配（第3列）
                # 更新现有项
                self.client_tree.item(item, values=(
                    values[0],  # 保持选择状态不变
                    client_info.get('hostname', '未知'),
                    client_info.get('ip', '未知'),
                    client_info.get('mac', '未知'),
                    client_info.get('status', '未知'),
                    client_info.get('last_seen', '未知'),
                    client_info.get('version', '未知')
                ))
                return
                
        # 添加新项
        self.client_tree.insert('', 'end', values=(
            '',  # 选择列初始为空
            client_info.get('hostname', '未知'),
            client_info.get('ip', '未知'),
            client_info.get('mac', '未知'),
            client_info.get('status', '未知'),
            client_info.get('last_seen', '未知'),
            client_info.get('version', '未知')
        ), tags=(client_info['ip'],))
        
        # 自动按计算机名排序
        self.sort_treeview('hostname', False)
        
    def on_client_double_click(self, event):
        """双击客户端事件"""
        item = self.client_tree.selection()[0] if self.client_tree.selection() else None
        if item:
            values = self.client_tree.item(item, 'values')
            self.show_client_details(values)
            
    def on_client_select(self, event):
        """客户端选择事件"""
        self.selected_clients = []
        for item in self.client_tree.selection():
            values = self.client_tree.item(item, 'values')
            self.selected_clients.append({
                'hostname': values[1],  # 第2列是计算机名
                'ip': values[2],       # 第3列是IP
                'mac': values[3]       # 第4列是MAC
            })
        
        # 更新选择状态显示
        count = len(self.selected_clients)
        if count == 0:
            self.selection_count_var.set("未选择任何客户端")
        else:
            self.selection_count_var.set(f"已选择 {count} 个客户端")
            
    def show_client_details(self, client_values):
        """显示客户端详情"""
        detail_window = tk.Toplevel(self.root)
        detail_window.title("客户端详情")
        detail_window.geometry("500x400")
        
        # 获取更详细的客户端信息
        detailed_info = self.get_detailed_client_info(client_values[2])  # 第3列是IP
        
        info_text = f"""客户端详细信息:

计算机名: {client_values[1]}
IP地址: {client_values[2]}
MAC地址: {client_values[3]}
状态: {client_values[4]}
最后在线: {client_values[5]}
客户端版本: {client_values[6]}

{detailed_info}"""
        
        text_widget = scrolledtext.ScrolledText(detail_window, wrap=tk.WORD)
        text_widget.insert(tk.END, info_text)
        text_widget.config(state=tk.DISABLED)
        text_widget.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
        
    def get_detailed_client_info(self, ip):
        """获取详细的客户端信息"""
        try:
            response = self.send_remote_command(ip, 'get_detailed_info')
            if response and response.get('status') == 'success':
                system_info = response.get('system_info', {})
                network_info = response.get('network_info', {})
                client_info = response.get('client_info', {})
                
                return f"""系统信息:
    操作系统: {system_info.get('os', '未知')}
    CPU使用率: {system_info.get('cpu_usage', '未知')}
    内存使用: {system_info.get('memory_usage', '未知')}
    磁盘空间: {system_info.get('disk_space', '未知')}

网络配置:
    子网掩码: {network_info.get('subnet_mask', '未知')}
        网关: {network_info.get('gateway', '未知')}
        DNS: {', '.join(network_info.get('dns_servers', []))}
        适配器: {network_info.get('adapter', '未知')}"""
            else:
                return "无法获取详细系统信息"
            
        except:
            return "无法获取详细系统信息"
            
    def show_selected_details(self):
        """显示选中客户端的详情"""
        if not self.selected_clients:
            messagebox.showinfo("提示", "请先选择客户端")
            return
            
        if len(self.selected_clients) == 1:
            client = self.selected_clients[0]
            # 在树中找到对应的值
            for item in self.client_tree.get_children():
                values = self.client_tree.item(item, 'values')
                if values[2] == client['ip']:  # 第3列是IP
                    self.show_client_details(values)
                    break
        else:
            messagebox.showinfo("提示", "请只选择一个客户端查看详情")
        
    def refresh_selected_status(self):
        """刷新选中客户端状态"""
        if not self.selected_clients:
            messagebox.showinfo("提示", "请先选择客户端")
            return
            
        self.log_message(f"开始刷新 {len(self.selected_clients)} 个客户端状态...", "INFO")
        
        # 使用线程池并发刷新状态
        with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
            futures = []
            for client in self.selected_clients:
                future = executor.submit(self.check_single_client_status, client)
                futures.append(future)
                
            for future in concurrent.futures.as_completed(futures):
                try:
                    result = future.result()
                    if result:
                        self.log_message(f"客户端 {result['hostname']} 状态: {result['status']}", "INFO")
                except:
                    pass

    def check_single_client_status(self, client):
        """检查单个客户端状态"""
        try:
            response = self.send_remote_command(client['ip'], 'get_info')
            if response and response.get('status') == 'success':
                client['status'] = 'online'
                # 更新最后在线时间
                for item in self.client_tree.get_children():
                    values = self.client_tree.item(item, 'values')
                    if values[2] == client['ip']:  # 第3列是IP
                        new_values = list(values)
                        new_values[4] = 'online'  # 第5列是状态
                        new_values[5] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")  # 第6列是最后在线
                        self.client_tree.item(item, values=new_values)
                        break
            else:
                client['status'] = 'offline'
                
            return client
            
        except:
            client['status'] = 'offline'
            return client

    def send_remote_command(self, ip, command_type, **kwargs):
        """发送远程命令到客户端 - 增强错误处理和并发支持"""
        max_retries = 2
        retry_delay = 1  # 秒
        
        for attempt in range(max_retries + 1):
            try:
                self.log_message(f"发送命令到 {ip}: {command_type} (尝试 {attempt + 1}/{max_retries + 1})", "DEBUG")
                
                # 为每个连接创建新的socket，避免并发问题
                sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                sock.settimeout(15)  # 增加超时时间
                
                try:
                    sock.connect((ip, self.server_config['client_port']))
                    
                    # 构建命令请求
                    request = {
                        'action': command_type,
                        'timestamp': datetime.now().isoformat(),
                        **kwargs
                    }
                    
                    request_data = json.dumps(request, ensure_ascii=False).encode('utf-8')
                    sock.send(request_data)
                    
                    # 接收响应
                    response_data = b""
                    sock.settimeout(10)  # 接收数据的超时时间
                    
                    while True:
                        try:
                            chunk = sock.recv(4096)
                            if not chunk:
                                break
                            response_data += chunk
                            # 检查是否接收完整（简单的结束判断）
                            if len(chunk) < 4096:
                                break
                        except socket.timeout:
                            break
                    
                    if response_data:
                        try:
                            response = json.loads(response_data.decode('utf-8'))
                            self.log_message(f"收到响应 from {ip}: {response.get('status', 'unknown')}", "DEBUG")
                            sock.close()
                            return response
                        except json.JSONDecodeError as e:
                            self.log_message(f"JSON解析失败 from {ip}: {response_data}", "DEBUG")
                            if attempt < max_retries:
                                self.log_message(f"重试命令...", "DEBUG")
                                time.sleep(retry_delay)
                                continue
                            sock.close()
                            return {'status': 'error', 'message': f'响应格式错误: {str(e)}'}
                    else:
                        self.log_message(f"无响应数据 from {ip}", "DEBUG")
                        if attempt < max_retries:
                            self.log_message(f"重试命令...", "DEBUG")
                            time.sleep(retry_delay)
                            continue
                        sock.close()
                        return {'status': 'error', 'message': '无响应数据'}
                        
                except socket.timeout:
                    self.log_message(f"连接 {ip} 超时", "DEBUG")
                    sock.close()
                    if attempt < max_retries:
                        self.log_message(f"重试命令...", "DEBUG")
                        time.sleep(retry_delay)
                        continue
                    return {'status': 'error', 'message': '连接超时'}
                except ConnectionRefusedError:
                    self.log_message(f"连接被拒绝 {ip}", "DEBUG")
                    sock.close()
                    if attempt < max_retries:
                        self.log_message(f"重试命令...", "DEBUG")
                        time.sleep(retry_delay)
                        continue
                    return {'status': 'error', 'message': '连接被拒绝'}
                except Exception as e:
                    self.log_message(f"连接错误 {ip}: {str(e)}", "DEBUG")
                    sock.close()
                    if attempt < max_retries:
                        self.log_message(f"重试命令...", "DEBUG")
                        time.sleep(retry_delay)
                        continue
                    return {'status': 'error', 'message': f'连接错误: {str(e)}'}
                    
            except Exception as e:
                self.log_message(f"发送命令到 {ip} 失败: {str(e)}", "ERROR")
                if attempt < max_retries:
                    self.log_message(f"重试命令...", "DEBUG")
                    time.sleep(retry_delay)
                    continue
                return {'status': 'error', 'message': f'连接失败: {str(e)}'}

    def remote_reboot(self):
        """远程重启 - 修复计数显示"""
        if not self.selected_clients:
            messagebox.showinfo("提示", "请先选择客户端")
            return
            
        if messagebox.askyesno("确认", f"确定要重启 {len(self.selected_clients)} 台计算机吗？"):
            success_count = 0
            total_count = len(self.selected_clients)
            
            for client in self.selected_clients:
                response = self.send_remote_command(client['ip'], 'reboot')
                if response and response.get('status') == 'success':
                    success_count += 1
                    self.log_message(f"成功发送重启命令到: {client['hostname']}", "INFO")
                else:
                    error_msg = response.get('message', '未知错误') if response else '无响应'
                    self.log_message(f"发送重启命令到 {client['hostname']} 失败: {error_msg}", "ERROR")
            
            failed_count = total_count - success_count
            self.show_info_message("操作完成", f"重启命令发送完成:\n成功: {success_count}\n失败: {failed_count}")

    def remote_shutdown(self):
        """远程关机 - 修复计数显示"""
        if not self.selected_clients:
            messagebox.showinfo("提示", "请先选择客户端")
            return
            
        if messagebox.askyesno("确认", f"确定要关闭 {len(self.selected_clients)} 台计算机吗？"):
            success_count = 0
            total_count = len(self.selected_clients)
            
            for client in self.selected_clients:
                response = self.send_remote_command(client['ip'], 'shutdown')
                if response and response.get('status') == 'success':
                    success_count += 1
                    self.log_message(f"成功发送关机命令到: {client['hostname']}", "INFO")
                else:
                    error_msg = response.get('message', '未知错误') if response else '无响应'
                    self.log_message(f"发送关机命令到 {client['hostname']} 失败: {error_msg}", "ERROR")
            
            failed_count = total_count - success_count
            self.show_info_message("操作完成", f"关机命令发送完成:\n成功: {success_count}\n失败: {failed_count}")

    def batch_modify_ip(self):
        """批量修改IP地址"""
        if not self.selected_clients:
            messagebox.showinfo("提示", "请先选择要修改的客户端")
            return
            
        # 创建IP修改窗口
        self.create_ip_modify_window()

    def create_ip_modify_window(self):
        """创建IP修改窗口 - 修复版：确保每个客户端有独立的IP偏移量"""
        ip_window = tk.Toplevel(self.root)
        ip_window.title("批量修改IP地址")
        ip_window.geometry("700x700")
        ip_window.transient(self.root)
        ip_window.grab_set()
        
        # 防止窗口卡死
        ip_window.protocol("WM_DELETE_WINDOW", lambda: self.safe_destroy(ip_window))
        
        # IP分配方式
        ttk.Label(ip_window, text="IP分配方式:").pack(anchor=tk.W, padx=10, pady=(10, 5))
        
        ip_method = tk.StringVar(value="sequential")
        method_frame = ttk.Frame(ip_window)
        method_frame.pack(fill=tk.X, padx=10, pady=5)
        
        ttk.Radiobutton(method_frame, text="顺序分配", 
                       variable=ip_method, value="sequential").pack(anchor=tk.W)
        ttk.Radiobutton(method_frame, text="手动指定", 
                       variable=ip_method, value="manual").pack(anchor=tk.W)
        
        # 网络配置区域
        config_frame = ttk.LabelFrame(ip_window, text="网络配置")
        config_frame.pack(fill=tk.X, padx=10, pady=5)
        
        # 第一行：起始IP和子网掩码
        row1_frame = ttk.Frame(config_frame)
        row1_frame.pack(fill=tk.X, padx=10, pady=5)
        
        ttk.Label(row1_frame, text="起始IP:").grid(row=0, column=0, sticky=tk.W)
        start_ip_var = tk.StringVar(value="192.168.1.100")
        start_ip_entry = ttk.Entry(row1_frame, textvariable=start_ip_var, width=15)
        start_ip_entry.grid(row=0, column=1, padx=5, sticky=tk.W)
        
        ttk.Label(row1_frame, text="子网掩码:").grid(row=0, column=2, sticky=tk.W, padx=(20, 0))
        subnet_var = tk.StringVar(value="255.255.255.0")
        subnet_entry = ttk.Entry(row1_frame, textvariable=subnet_var, width=15)
        subnet_entry.grid(row=0, column=3, padx=5, sticky=tk.W)
        
        # 第二行：网关
        row2_frame = ttk.Frame(config_frame)
        row2_frame.pack(fill=tk.X, padx=10, pady=5)
        
        ttk.Label(row2_frame, text="网关:").grid(row=0, column=0, sticky=tk.W)
        gateway_var = tk.StringVar(value="192.168.1.1")
        gateway_entry = ttk.Entry(row2_frame, textvariable=gateway_var, width=15)
        gateway_entry.grid(row=0, column=1, padx=5, sticky=tk.W)
        
        # 第三行：DNS服务器
        row3_frame = ttk.Frame(config_frame)
        row3_frame.pack(fill=tk.X, padx=10, pady=5)
        
        ttk.Label(row3_frame, text="DNS服务器:").pack(anchor=tk.W, padx=10, pady=5)
        
        dns_frame = ttk.Frame(row3_frame)
        dns_frame.pack(fill=tk.X, padx=10, pady=5)
        
        dns1_var = tk.StringVar(value="8.8.8.8")
        dns1_entry = ttk.Entry(dns_frame, textvariable=dns1_var, width=15)
        dns1_entry.pack(side=tk.LEFT, padx=5)
        
        ttk.Label(dns_frame, text="备用DNS:").pack(side=tk.LEFT, padx=(20, 0))
        dns2_var = tk.StringVar(value="114.114.114.114")
        dns2_entry = ttk.Entry(dns_frame, textvariable=dns2_var, width=15)
        dns2_entry.pack(side=tk.LEFT, padx=5)
        
        # 客户端列表
        ttk.Label(ip_window, text="客户端列表 (勾选要修改的客户端):").pack(anchor=tk.W, padx=10, pady=(10, 5))
        
        list_frame = ttk.Frame(ip_window)
        list_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
        
        # 创建表格显示客户端和分配的IP
        columns = ('select', 'hostname', 'current_ip', 'new_ip', 'status')
        ip_tree = ttk.Treeview(list_frame, columns=columns, show='headings', height=8)
        
        ip_tree.heading('select', text='选择')
        ip_tree.heading('hostname', text='计算机名')
        ip_tree.heading('current_ip', text='当前IP')
        ip_tree.heading('new_ip', text='新IP')
        ip_tree.heading('status', text='状态')
        
        ip_tree.column('select', width=50, anchor='center')
        ip_tree.column('hostname', width=120)
        ip_tree.column('current_ip', width=100)
        ip_tree.column('new_ip', width=100)
        ip_tree.column('status', width=80)
        
        # 存储客户端原始信息和独立的新IP
        original_clients = self.selected_clients.copy()
        client_new_ips = {}  # 存储每个客户端的新IP {hostname: new_ip}
        
        # 初始化：为每个客户端设置独立的新IP
        base_ip_parts = start_ip_var.get().split('.')
        base_ip_num = int(base_ip_parts[3]) if len(base_ip_parts) == 4 and base_ip_parts[3].isdigit() else 100
        
        for i, client in enumerate(original_clients):
            new_ip_num = base_ip_num + i
            if new_ip_num > 254:
                new_ip_num = 254
            new_ip = f"{base_ip_parts[0]}.{base_ip_parts[1]}.{base_ip_parts[2]}.{new_ip_num}"
            client_new_ips[client['hostname']] = new_ip

        def refresh_ip_display():
            """刷新IP显示 - 修复版：只修改起始IP时重新计算，手动编辑的不受影响"""
            # 只有在顺序分配模式下，修改起始IP才重新计算所有IP
            if ip_method.get() == "sequential":
                base_ip_parts = start_ip_var.get().split('.')
                if len(base_ip_parts) != 4 or not all(part.isdigit() for part in base_ip_parts):
                    return
                    
                base_ip_num = int(base_ip_parts[3])
                
                # 检查是否有重复的IP
                used_ips = set()
                
                for i, item in enumerate(ip_tree.get_children()):
                    hostname = ip_tree.set(item, 'hostname')
                    
                    # 只有在没有手动编辑过的情况下才重新计算
                    if hostname not in client_new_ips or ip_tree.set(item, 'status') == '待修改':
                        new_ip_num = base_ip_num + i
                        if new_ip_num > 254:
                            new_ip_num = 254
                        
                        # 确保IP不重复
                        while True:
                            new_ip = f"{base_ip_parts[0]}.{base_ip_parts[1]}.{base_ip_parts[2]}.{new_ip_num}"
                            if new_ip not in used_ips:
                                used_ips.add(new_ip)
                                client_new_ips[hostname] = new_ip
                                break
                            new_ip_num += 1
                            if new_ip_num > 254:
                                new_ip_num = base_ip_num
                    
                    # 更新显示
                    ip_tree.set(item, 'new_ip', client_new_ips.get(hostname, ''))
                    ip_tree.set(item, 'status', '待修改')

        def initial_fill():
            """初始填充数据"""
            for i, client in enumerate(original_clients):
                ip_tree.insert('', 'end', values=(
                    '✓',  # 默认选中
                    client['hostname'],
                    client['ip'],
                    client_new_ips.get(client['hostname'], ''),
                    '待修改'
                ))

        # 执行初始填充
        initial_fill()
        
        scrollbar = ttk.Scrollbar(list_frame, orient=tk.VERTICAL, command=ip_tree.yview)
        ip_tree.configure(yscrollcommand=scrollbar.set)
        
        ip_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        
        def on_treeview_click(event):
            """处理树形视图点击事件 - 用于选择列"""
            item = ip_tree.identify_row(event.y)
            column = ip_tree.identify_column(event.x)
            
            if item and column == '#1':  # 选择列
                # 切换选择状态
                current_value = ip_tree.set(item, 'select')
                new_value = '✓' if current_value != '✓' else ''
                ip_tree.set(item, 'select', new_value)
                return 'break'  # 阻止默认选择行为
        
        ip_tree.bind('<Button-1>', on_treeview_click)
        
        def on_start_ip_change(*args):
            """起始IP变化时的回调"""
            if self.is_valid_ip(start_ip_var.get()):
                refresh_ip_display()

        def on_method_change():
            """分配方式变化时的回调"""
            refresh_ip_display()

        def start_edit(event):
            """开始编辑新IP - 修复版：只修改单个IP，不影响其他"""
            item = ip_tree.identify_row(event.y)
            column = ip_tree.identify_column(event.x)
            
            if item and column == '#4':  # new_ip列
                x, y, width, height = ip_tree.bbox(item, column)
                
                # 创建编辑框
                entry = ttk.Entry(ip_window)
                entry.place(x=x, y=y, width=width, height=height)
                
                current_value = ip_tree.set(item, 'new_ip')
                entry.insert(0, current_value)
                entry.select_range(0, tk.END)
                entry.focus()
                
                def save_edit(event=None):
                    """保存编辑结果 - 只修改当前IP，不影响其他"""
                    new_ip_value = entry.get()
                    
                    if self.is_valid_ip(new_ip_value):
                        # 只更新当前客户端的新IP，不影响其他客户端
                        hostname = ip_tree.set(item, 'hostname')
                        client_new_ips[hostname] = new_ip_value
                        
                        # 更新显示
                        ip_tree.set(item, 'new_ip', new_ip_value)
                        ip_tree.set(item, 'status', '待修改')
                    
                    entry.destroy()
                
                def cancel_edit(event=None):
                    """取消编辑"""
                    entry.destroy()
                
                entry.bind('<Return>', save_edit)
                entry.bind('<FocusOut>', save_edit)
                entry.bind('<Escape>', cancel_edit)

        # 绑定事件
        start_ip_var.trace('w', on_start_ip_change)
        ip_method.trace('w', lambda *args: on_method_change())
        ip_tree.bind('<Double-1>', start_edit)
        
        def adjust_offset(hostname, delta):
            """调整指定客户端的IP偏移量"""
            if hostname in client_new_ips:
                current_ip = client_new_ips[hostname]
                ip_parts = current_ip.split('.')
                if len(ip_parts) == 4:
                    try:
                        new_ip_num = int(ip_parts[3]) + delta
                        if 1 <= new_ip_num <= 254:
                            new_ip = f"{ip_parts[0]}.{ip_parts[1]}.{ip_parts[2]}.{new_ip_num}"
                            client_new_ips[hostname] = new_ip
                            
                            # 更新显示
                            for item in ip_tree.get_children():
                                if ip_tree.set(item, 'hostname') == hostname:
                                    ip_tree.set(item, 'new_ip', new_ip)
                                    ip_tree.set(item, 'status', '待修改')
                                    break
                    except ValueError:
                        pass
        
        def create_offset_controls():
            """创建偏移量控制界面"""
            control_frame = ttk.LabelFrame(ip_window, text="IP偏移量调整")
            control_frame.pack(fill=tk.X, padx=10, pady=5)
            
            # 说明文字
            ttk.Label(control_frame, text="双击新IP可手动编辑，或使用按钮调整:").pack(anchor=tk.W, padx=10, pady=5)
            
            # 为每个客户端创建调整按钮
            client_frame = ttk.Frame(control_frame)
            client_frame.pack(fill=tk.X, padx=10, pady=5)
            
            for i, client in enumerate(original_clients):
                if i % 3 == 0:
                    row_frame = ttk.Frame(client_frame)
                    row_frame.pack(fill=tk.X, pady=2)
                
                client_btn_frame = ttk.Frame(row_frame)
                client_btn_frame.pack(side=tk.LEFT, padx=5)
                
                ttk.Label(client_btn_frame, text=f"{client['hostname']}:").pack(side=tk.LEFT)
                ttk.Button(client_btn_frame, text="-", width=3,
                          command=lambda h=client['hostname']: adjust_offset(h, -1)).pack(side=tk.LEFT, padx=2)
                ttk.Button(client_btn_frame, text="+", width=3,
                          command=lambda h=client['hostname']: adjust_offset(h, 1)).pack(side=tk.LEFT, padx=2)
        
        create_offset_controls()
        
        def validate_ips():
            """验证所有IP地址"""
            invalid_ips = []
            duplicate_ips = []
            ip_set = set()
            
            for item in ip_tree.get_children():
                if ip_tree.set(item, 'select') == '✓':  # 只验证选中的
                    values = ip_tree.item(item, 'values')
                    new_ip = values[3]  # 第4列是新IP
                    hostname = values[1]  # 第2列是计算机名
                    
                    # 检查IP格式
                    if not self.is_valid_ip(new_ip):
                        invalid_ips.append(f"{hostname}: {new_ip}")
                        ip_tree.set(item, 'status', 'IP格式错误')
                        continue
                    
                    # 检查重复IP
                    if new_ip in ip_set:
                        duplicate_ips.append(f"{hostname}: {new_ip}")
                        ip_tree.set(item, 'status', 'IP重复')
                    else:
                        ip_set.add(new_ip)
                        ip_tree.set(item, 'status', '验证通过')
            
            return invalid_ips, duplicate_ips
        
        def apply_ip_changes():
            """应用IP修改 - 修复版：确保每个客户端都发送命令"""
            # 验证IP地址
            invalid_ips, duplicate_ips = validate_ips()
            
            if invalid_ips:
                messagebox.showerror("错误", f"以下IP地址格式无效:\n" + "\n".join(invalid_ips), parent=ip_window)
                return
            
            if duplicate_ips:
                messagebox.showerror("错误", f"以下IP地址重复:\n" + "\n".join(duplicate_ips), parent=ip_window)
                return
            
            # 获取选中的客户端
            selected_clients_info = []
            for item in ip_tree.get_children():
                if ip_tree.set(item, 'select') == '✓':  # 检查选择列
                    values = ip_tree.item(item, 'values')
                    hostname = values[1]    # 第2列是计算机名
                    current_ip = values[2]  # 第3列是当前IP
                    new_ip = values[3]      # 第4列是新IP
                    
                    # 找到对应的原始客户端信息
                    for client in original_clients:
                        if client['hostname'] == hostname and client['ip'] == current_ip:
                            selected_clients_info.append({
                                'hostname': hostname,
                                'current_ip': current_ip,
                                'new_ip': new_ip,
                                'item': item  # 保存item引用用于更新状态
                            })
                            break
            
            if not selected_clients_info:
                messagebox.showinfo("提示", "请至少选择一个要修改的客户端", parent=ip_window)
                return
            
            # 准备DNS服务器列表
            dns_servers = [dns1_var.get().strip()]
            if dns2_var.get().strip():
                dns_servers.append(dns2_var.get().strip())
            
            success_count = 0
            total_count = len(selected_clients_info)
            
            def send_single_ip_command(client_info):
                """发送单个IP修改命令 - 修复版：每个客户端只发送一次命令"""
                nonlocal success_count
                
                hostname = client_info['hostname']
                current_ip = client_info['current_ip']
                new_ip = client_info['new_ip']
                item = client_info['item']
                
                try:
                    # 判断发送哪种命令：IP修改或DNS更新
                    if current_ip != new_ip:
                        # 发送修改IP命令（包含DNS配置）
                        response = self.send_remote_command(
                            current_ip,  # 使用原始IP发送命令
                            'update_ip',
                            new_ip=new_ip,
                            gateway=gateway_var.get(),
                            subnet_mask=subnet_var.get(),
                            dns_servers=dns_servers
                        )
                        
                        # 修复成功判断逻辑
                        if response and response.get('status') == 'success':
                            success_count += 1
                            ip_tree.set(item, 'status', '修改成功')
                            self.log_message(f"成功发送IP修改命令到 {hostname}: {current_ip} -> {new_ip}", "INFO")
                            
                            # 更新客户端数据
                            self.update_client_ip_in_memory(current_ip, new_ip, hostname)
                            
                        else:
                            error_msg = response.get('message', '未知错误') if response else '无响应'
                            ip_tree.set(item, 'status', '修改失败')
                            self.log_message(f"发送IP修改命令到 {hostname} 失败: {error_msg}", "ERROR")
                            
                    else:
                        # IP未变化，只更新DNS配置
                        response = self.send_remote_command(
                            current_ip,
                            'update_ip',  # 注意：这里应该使用update_ip而不是update_dns
                            new_ip=new_ip,  # 即使IP相同也要传
                            gateway=gateway_var.get(),
                            subnet_mask=subnet_var.get(),
                            dns_servers=dns_servers
                        )
                        
                        if response and response.get('status') == 'success':
                            ip_tree.set(item, 'status', 'DNS更新成功')
                            success_count += 1  # DNS更新成功也算成功
                            self.log_message(f"成功更新 {hostname} 的DNS配置", "INFO")
                        else:
                            error_msg = response.get('message', '未知错误') if response else '无响应'
                            ip_tree.set(item, 'status', 'DNS更新失败')
                            self.log_message(f"更新 {hostname} 的DNS配置失败: {error_msg}", "ERROR")
                            
                except Exception as e:
                    if current_ip != new_ip:
                        ip_tree.set(item, 'status', '连接失败')
                    else:
                        ip_tree.set(item, 'status', 'DNS连接失败')
                    self.log_message(f"连接到 {hostname} 失败: {str(e)}", "ERROR")
            
            # 使用线程池并发执行，但确保每个客户端独立处理
            def execute_commands():
                # 创建线程池并发执行所有命令
                with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
                    # 提交所有任务
                    futures = []
                    for client_info in selected_clients_info:
                        future = executor.submit(send_single_ip_command, client_info)
                        futures.append(future)
                    
                    # 等待所有任务完成
                    concurrent.futures.wait(futures)
                
                # 完全刷新主界面客户端列表
                self.refresh_client_tree_completely()
                
                # 更新选中的客户端列表
                ip_mapping = {info['current_ip']: info['new_ip'] for info in selected_clients_info}
                for i, client in enumerate(self.selected_clients):
                    if client['ip'] in ip_mapping:
                        self.selected_clients[i]['ip'] = ip_mapping[client['ip']]
                
                failed_count = total_count - success_count
                
                # 在当前窗口显示结果，而不是主窗口
                result_message = f"网络配置修改完成:\n成功: {success_count}\n失败: {failed_count}"
                
                # 使用 after 方法确保在UI线程中显示消息框
                ip_window.after(0, lambda: self.show_ip_modify_result(ip_window, result_message))
            
            # 在新线程中执行命令
            threading.Thread(target=execute_commands, daemon=True).start()
        
        # 按钮框架
        btn_frame = ttk.Frame(ip_window)
        btn_frame.pack(fill=tk.X, padx=10, pady=10)
        
        ttk.Button(btn_frame, text="全选", 
                  command=lambda: [ip_tree.set(item, 'select', '✓') for item in ip_tree.get_children()]).pack(side=tk.LEFT, padx=5)
        ttk.Button(btn_frame, text="取消全选", 
                  command=lambda: [ip_tree.set(item, 'select', '') for item in ip_tree.get_children()]).pack(side=tk.LEFT, padx=5)
        ttk.Button(btn_frame, text="验证IP地址", 
                  command=validate_ips).pack(side=tk.LEFT, padx=5)
        ttk.Button(btn_frame, text="批量+1", 
                  command=lambda: [adjust_offset(client['hostname'], 1) for client in original_clients]).pack(side=tk.LEFT, padx=5)
        ttk.Button(btn_frame, text="批量-1", 
                  command=lambda: [adjust_offset(client['hostname'], -1) for client in original_clients]).pack(side=tk.LEFT, padx=5)
        ttk.Button(btn_frame, text="应用修改", 
                  command=apply_ip_changes).pack(side=tk.RIGHT, padx=5)
        ttk.Button(btn_frame, text="取消", 
                  command=lambda: self.safe_destroy(ip_window)).pack(side=tk.RIGHT, padx=5)

    def show_ip_modify_result(self, parent_window, message):
        """在IP修改窗口中显示结果"""
        messagebox.showinfo("操作完成", message, parent=parent_window)
        # 用户手动关闭窗口，不自动关闭

    def safe_destroy(self, window):
        """安全销毁窗口，避免卡死"""
        try:
            window.destroy()
        except:
            pass

    def update_client_ip_in_memory(self, old_ip, new_ip, hostname):
        """在内存中更新客户端IP地址"""
        if old_ip in self.clients:
            client_info = self.clients.pop(old_ip)
            client_info['ip'] = new_ip
            self.clients[new_ip] = client_info
            self.log_message(f"更新客户端IP映射: {hostname} {old_ip} -> {new_ip}", "INFO")

    def refresh_client_tree_completely(self):
        """完全刷新客户端树形视图"""
        # 清空现有显示
        for item in self.client_tree.get_children():
            self.client_tree.delete(item)
        
        # 按计算机名排序后重新添加
        sorted_clients = sorted(self.clients.values(), key=lambda x: x.get('hostname', '').lower())
        for client_info in sorted_clients:
            self.client_tree.insert('', 'end', values=(
                '',  # 选择列初始为空
                client_info.get('hostname', '未知'),
                client_info.get('ip', '未知'),
                client_info.get('mac', '未知'),
                client_info.get('status', '未知'),
                client_info.get('last_seen', '未知'),
                client_info.get('version', '未知')
            ), tags=(client_info['ip'],))

    def batch_modify_hostname(self):
        """批量修改计算机名 - 修复版"""
        if not self.selected_clients:
            messagebox.showinfo("提示", "请先选择要修改的客户端")
            return
            
        # 创建计算机名修改窗口
        self.create_hostname_modify_window()

    def create_hostname_modify_window(self):
        """创建计算机名修改窗口 - 修复版：确保每个客户端都发送命令"""
        hostname_window = tk.Toplevel(self.root)
        hostname_window.title("批量修改计算机名")
        hostname_window.geometry("600x580")
        hostname_window.transient(self.root)
        hostname_window.grab_set()
        
        # 防止窗口卡死
        hostname_window.protocol("WM_DELETE_WINDOW", lambda: self.safe_destroy(hostname_window))
        
        # 计算机名模板
        ttk.Label(hostname_window, text="计算机名模板:").pack(anchor=tk.W, padx=10, pady=(10, 5))
        template_var = tk.StringVar(value="PC-{num:03d}")
        template_entry = ttk.Entry(hostname_window, textvariable=template_var, width=30)
        template_entry.pack(fill=tk.X, padx=10, pady=5)
        
        ttk.Label(hostname_window, text="使用 {num} 作为序号占位符，可使用格式化如 {num:03d}").pack(anchor=tk.W, padx=10)
        
        # 起始序号
        ttk.Label(hostname_window, text="起始序号:").pack(anchor=tk.W, padx=10, pady=(10, 5))
        start_num_var = tk.StringVar(value="1")
        start_num_entry = ttk.Entry(hostname_window, textvariable=start_num_var, width=10)
        start_num_entry.pack(anchor=tk.W, padx=10, pady=5)
        
        # 客户端列表
        ttk.Label(hostname_window, text="客户端列表 (勾选要修改的客户端):").pack(anchor=tk.W, padx=10, pady=(10, 5))
        
        list_frame = ttk.Frame(hostname_window)
        list_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
        
        # 创建表格显示客户端和新计算机名
        columns = ('select', 'hostname', 'ip', 'new_hostname', 'status')
        hostname_tree = ttk.Treeview(list_frame, columns=columns, show='headings', height=10)
        
        hostname_tree.heading('select', text='选择')
        hostname_tree.heading('hostname', text='当前计算机名')
        hostname_tree.heading('ip', text='IP地址')
        hostname_tree.heading('new_hostname', text='新计算机名')
        hostname_tree.heading('status', text='状态')
        
        hostname_tree.column('select', width=50, anchor='center')
        hostname_tree.column('hostname', width=120)
        hostname_tree.column('ip', width=100)
        hostname_tree.column('new_hostname', width=120)
        hostname_tree.column('status', width=80)
        
        # 存储客户端原始信息
        original_clients = self.selected_clients.copy()
        
        def refresh_hostnames():
            """刷新计算机名显示"""
            # 清空现有数据
            for item in hostname_tree.get_children():
                hostname_tree.delete(item)
            
            # 重新填充数据
            try:
                start_num = int(start_num_var.get())
                template = template_var.get()
                
                for i, client in enumerate(original_clients):
                    # 使用格式化字符串生成新计算机名
                    try:
                        new_hostname = template.format(num=start_num + i)
                    except:
                        # 如果格式化失败，使用简单替换
                        new_hostname = template.replace("{num}", str(start_num + i))
                    
                    hostname_tree.insert('', 'end', values=(
                        '✓',  # 默认选中
                        client['hostname'],
                        client['ip'],
                        new_hostname,
                        '待修改'
                    ))
            except ValueError:
                self.log_message("起始序号必须是数字", "ERROR")
        
        # 初始填充
        refresh_hostnames()
        
        scrollbar = ttk.Scrollbar(list_frame, orient=tk.VERTICAL, command=hostname_tree.yview)
        hostname_tree.configure(yscrollcommand=scrollbar.set)
        
        hostname_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        
        def on_treeview_click(event):
            """处理树形视图点击事件 - 用于选择列"""
            item = hostname_tree.identify_row(event.y)
            column = hostname_tree.identify_column(event.x)
            
            if item and column == '#1':  # 选择列
                # 切换选择状态
                current_value = hostname_tree.set(item, 'select')
                new_value = '✓' if current_value != '✓' else ''
                hostname_tree.set(item, 'select', new_value)
                return 'break'  # 阻止默认选择行为
        
        hostname_tree.bind('<Button-1>', on_treeview_click)
        
        def start_edit(event):
            """开始编辑新计算机名"""
            item = hostname_tree.identify_row(event.y)
            column = hostname_tree.identify_column(event.x)
            
            if item and column == '#4':  # new_hostname列
                x, y, width, height = hostname_tree.bbox(item, column)
                
                # 创建编辑框
                entry = ttk.Entry(hostname_window)
                entry.place(x=x, y=y, width=width, height=height)
                
                current_value = hostname_tree.set(item, 'new_hostname')
                entry.insert(0, current_value)
                entry.select_range(0, tk.END)
                entry.focus()
                
                def save_edit(event=None):
                    """保存编辑结果"""
                    new_value = entry.get()
                    if new_value.strip():
                        hostname_tree.set(item, 'new_hostname', new_value.strip())
                        hostname_tree.set(item, 'status', '待修改')
                    entry.destroy()
                
                def cancel_edit(event=None):
                    """取消编辑"""
                    entry.destroy()
                
                entry.bind('<Return>', save_edit)
                entry.bind('<FocusOut>', save_edit)
                entry.bind('<Escape>', cancel_edit)
        
        hostname_tree.bind('<Double-1>', start_edit)
        
        # 进度显示区域
        progress_frame = ttk.Frame(hostname_window)
        progress_frame.pack(fill=tk.X, padx=10, pady=5)
        
        progress_var = tk.DoubleVar()
        progress_bar = ttk.Progressbar(progress_frame, variable=progress_var, maximum=100)
        progress_bar.pack(fill=tk.X, pady=5)
        
        status_var = tk.StringVar(value="就绪")
        status_label = ttk.Label(progress_frame, textvariable=status_var)
        status_label.pack()
        
        def update_progress(current, total, success, message):
            """更新进度回调"""
            progress = (current / total) * 100 if total > 0 else 0
            progress_var.set(progress)
            status_var.set(message)
            hostname_window.update()
            
            # 更新对应客户端的状态
            if current > 0 and current <= len(original_clients):
                item = hostname_tree.get_children()[current - 1]
                client = original_clients[current - 1]
                if "成功" in message and client['hostname'] in message:
                    hostname_tree.set(item, 'status', '修改成功')
                elif "失败" in message and client['hostname'] in message:
                    hostname_tree.set(item, 'status', '修改失败')
        
        def apply_hostname_changes():
            """应用计算机名修改 - 使用独立的模块"""
            # 获取选中的客户端
            selected_clients_info = []
            for item in hostname_tree.get_children():
                if hostname_tree.set(item, 'select') == '✓':  # 检查选择列
                    values = hostname_tree.item(item, 'values')
                    current_hostname = values[1]  # 第2列是当前计算机名
                    ip = values[2]                # 第3列是IP
                    new_hostname = values[3]      # 第4列是新计算机名
                    
                    if not new_hostname or new_hostname.strip() == '':
                        self.log_message(f"计算机名为空 for {current_hostname}", "ERROR")
                        continue
                    
                    # 找到对应的原始客户端信息
                    for client in original_clients:
                        if client['hostname'] == current_hostname and client['ip'] == ip:
                            selected_clients_info.append({
                                'ip': ip,
                                'current_hostname': current_hostname,
                                'new_hostname': new_hostname
                            })
                            break
            
            if not selected_clients_info:
                messagebox.showinfo("提示", "请至少选择一个要修改的客户端", parent=hostname_window)
                return
            
            # 使用独立的计算机名管理模块
            manager = HostnameManager(self.logger)
            
            def execute_batch_update():
                """在新线程中执行批量更新"""
                success_count, failed_count, detailed_results = manager.batch_update_hostnames(
                    selected_clients_info,
                    port=self.server_config['client_port'],
                    progress_callback=update_progress
                )
                
                # 更新主界面显示
                for result in detailed_results:
                    if result['success']:
                        # 更新客户端列表中的计算机名
                        ip = result['ip']
                        new_hostname = result['new_hostname']
                        
                        if ip in self.clients:
                            self.clients[ip]['hostname'] = new_hostname
                        
                        # 更新树形视图显示
                        for tree_item in self.client_tree.get_children():
                            tree_values = self.client_tree.item(tree_item, 'values')
                            if tree_values[2] == ip:  # 第3列是IP
                                new_tree_values = list(tree_values)
                                new_tree_values[1] = new_hostname  # 第2列是计算机名
                                self.client_tree.item(tree_item, values=new_tree_values)
                                break
                
                # 重新排序客户端列表
                self.root.after(0, lambda: self.sort_treeview('hostname', False))
                
                # 显示结果
                result_message = f"计算机名修改完成:\n成功: {success_count}\n失败: {failed_count}"
                self.root.after(0, lambda: self.show_hostname_modify_result(hostname_window, result_message))
            
            # 在新线程中执行
            threading.Thread(target=execute_batch_update, daemon=True).start()
        
        def show_hostname_modify_result(self, parent_window, message):
            """显示计算机名修改结果"""
            messagebox.showinfo("操作完成", message, parent=parent_window)
        
        # 按钮框架
        btn_frame = ttk.Frame(hostname_window)
        btn_frame.pack(fill=tk.X, padx=10, pady=10)
        
        ttk.Button(btn_frame, text="全选", 
                  command=lambda: [hostname_tree.set(item, 'select', '✓') for item in hostname_tree.get_children()]).pack(side=tk.LEFT, padx=5)
        ttk.Button(btn_frame, text="取消全选", 
                  command=lambda: [hostname_tree.set(item, 'select', '') for item in hostname_tree.get_children()]).pack(side=tk.LEFT, padx=5)
        ttk.Button(btn_frame, text="刷新预览", 
                  command=refresh_hostnames).pack(side=tk.LEFT, padx=5)
        ttk.Button(btn_frame, text="应用修改", 
                  command=apply_hostname_changes).pack(side=tk.RIGHT, padx=5)
        ttk.Button(btn_frame, text="取消", 
                  command=lambda: self.safe_destroy(hostname_window)).pack(side=tk.RIGHT, padx=5)

    def wake_on_lan(self):
        """网络唤醒 - 修复版：移除客户端检查，直接打开唤醒窗口"""
        # 直接打开唤醒管理窗口，不检查客户端
        self.create_wake_on_lan_window()

    def create_wake_on_lan_window(self):
        """创建网络唤醒管理窗口"""
        wake_window = tk.Toplevel(self.root)
        wake_window.title("网络唤醒管理")
        wake_window.geometry("800x600")
        wake_window.transient(self.root)
        wake_window.grab_set()

        # 主框架
        main_frame = ttk.Frame(wake_window, padding="10")
        main_frame.pack(fill=tk.BOTH, expand=True)

        # 分组管理区域
        group_frame = ttk.LabelFrame(main_frame, text="分组管理")
        group_frame.pack(fill=tk.X, padx=5, pady=5)

        # 分组操作按钮
        group_btn_frame = ttk.Frame(group_frame)
        group_btn_frame.pack(fill=tk.X, padx=5, pady=5)

        ttk.Button(group_btn_frame, text="新建分组", 
                  command=lambda: self.create_new_group()).pack(side=tk.LEFT, padx=2)
        ttk.Button(group_btn_frame, text="删除分组", 
                  command=lambda: self.delete_selected_group()).pack(side=tk.LEFT, padx=2)
        ttk.Button(group_btn_frame, text="重命名分组", 
                  command=lambda: self.rename_selected_group()).pack(side=tk.LEFT, padx=2)

        # 分组列表
        group_list_frame = ttk.Frame(group_frame)
        group_list_frame.pack(fill=tk.X, padx=5, pady=5)

        ttk.Label(group_list_frame, text="分组列表:").pack(anchor=tk.W)
        
        self.group_listbox = tk.Listbox(group_list_frame, height=4)
        self.group_listbox.pack(fill=tk.X, pady=5)
        self.group_listbox.bind('<<ListboxSelect>>', self.on_group_selected)

        # 客户端管理区域
        client_frame = ttk.LabelFrame(main_frame, text="客户端管理")
        client_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)

        # 客户端列表和分组客户端列表
        list_container = ttk.Frame(client_frame)
        list_container.pack(fill=tk.BOTH, expand=True)

        # 所有客户端列表
        left_frame = ttk.LabelFrame(list_container, text="所有客户端")
        left_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=5, pady=5)

        ttk.Label(left_frame, text="扫描到的客户端:").pack(anchor=tk.W, padx=5, pady=2)
        
        self.all_clients_listbox = tk.Listbox(left_frame, selectmode=tk.MULTIPLE)
        self.all_clients_listbox.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)

        # 添加客户端按钮
        add_remove_frame = ttk.Frame(list_container)
        add_remove_frame.pack(side=tk.LEFT, fill=tk.Y, padx=5, pady=5)

        ttk.Button(add_remove_frame, text="添加到分组 →", 
                  command=self.add_to_group).pack(pady=10)
        ttk.Button(add_remove_frame, text="← 从分组移除", 
                  command=self.remove_from_group).pack(pady=10)

        # 分组客户端列表
        right_frame = ttk.LabelFrame(list_container, text="分组客户端")
        right_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=5, pady=5)

        ttk.Label(right_frame, text="当前分组中的客户端:").pack(anchor=tk.W, padx=5, pady=2)
        
        self.group_clients_listbox = tk.Listbox(right_frame, selectmode=tk.MULTIPLE)
        self.group_clients_listbox.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)

        # 唤醒操作区域
        wake_frame = ttk.LabelFrame(main_frame, text="唤醒操作")
        wake_frame.pack(fill=tk.X, padx=5, pady=5)

        wake_btn_frame = ttk.Frame(wake_frame)
        wake_btn_frame.pack(fill=tk.X, padx=5, pady=10)

        ttk.Button(wake_btn_frame, text="唤醒选中客户端", 
                  command=lambda: self.wake_selected_clients()).pack(side=tk.LEFT, padx=5)
        ttk.Button(wake_btn_frame, text="唤醒整个分组", 
                  command=lambda: self.wake_entire_group()).pack(side=tk.LEFT, padx=5)
        ttk.Button(wake_btn_frame, text="刷新客户端列表", 
                  command=lambda: self.refresh_wake_clients()).pack(side=tk.LEFT, padx=5)

        # 状态显示
        self.wake_status_var = tk.StringVar(value="就绪")
        status_label = ttk.Label(wake_frame, textvariable=self.wake_status_var)
        status_label.pack(pady=5)

        # 初始化数据
        self.current_group = None
        self.refresh_wake_clients()
        self.refresh_group_list()

    def refresh_group_list(self):
        """刷新分组列表"""
        self.group_listbox.delete(0, tk.END)
        for group_name in self.wake_groups.keys():
            client_count = len(self.wake_groups[group_name])
            self.group_listbox.insert(tk.END, f"{group_name} ({client_count}台)")

    def refresh_wake_clients(self):
        """刷新客户端列表"""
        self.all_clients_listbox.delete(0, tk.END)
        for ip, client in self.clients.items():
            display_text = f"{client.get('hostname', '未知')} - {ip} - {client.get('mac', '未知')}"
            self.all_clients_listbox.insert(tk.END, display_text)

        # 刷新分组客户端列表
        self.refresh_group_clients()

    def refresh_group_clients(self):
        """刷新分组客户端列表"""
        self.group_clients_listbox.delete(0, tk.END)
        if self.current_group and self.current_group in self.wake_groups:
            for client in self.wake_groups[self.current_group]:
                display_text = f"{client.get('hostname', '未知')} - {client.get('ip', '未知')} - {client.get('mac', '未知')}"
                self.group_clients_listbox.insert(tk.END, display_text)

    def on_group_selected(self, event):
        """分组选择事件"""
        selection = self.group_listbox.curselection()
        if selection:
            selected_text = self.group_listbox.get(selection[0])
            # 提取分组名（去掉括号中的数量信息）
            group_name = selected_text.split(' (')[0]
            self.current_group = group_name
            self.refresh_group_clients()
            self.wake_status_var.set(f"已选择分组: {group_name}")

    def create_new_group(self):
        """创建新分组"""
        group_name = simpledialog.askstring("新建分组", "请输入分组名称:")
        if group_name:
            if group_name in self.wake_groups:
                messagebox.showwarning("警告", f"分组 '{group_name}' 已存在!")
                return
            self.wake_groups[group_name] = []
            self.save_wake_groups()
            self.refresh_group_list()
            self.wake_status_var.set(f"已创建分组: {group_name}")

    def delete_selected_group(self):
        """删除选中的分组"""
        if not self.current_group:
            messagebox.showinfo("提示", "请先选择一个分组")
            return
            
        if messagebox.askyesno("确认删除", f"确定要删除分组 '{self.current_group}' 吗？"):
            del self.wake_groups[self.current_group]
            self.current_group = None
            self.save_wake_groups()
            self.refresh_group_list()
            self.group_clients_listbox.delete(0, tk.END)
            self.wake_status_var.set("分组已删除")

    def rename_selected_group(self):
        """重命名选中的分组"""
        if not self.current_group:
            messagebox.showinfo("提示", "请先选择一个分组")
            return
            
        new_name = simpledialog.askstring("重命名分组", "请输入新的分组名称:", 
                                         initialvalue=self.current_group)
        if new_name and new_name != self.current_group:
            if new_name in self.wake_groups:
                messagebox.showwarning("警告", f"分组 '{new_name}' 已存在!")
                return
                
            self.wake_groups[new_name] = self.wake_groups.pop(self.current_group)
            self.current_group = new_name
            self.save_wake_groups()
            self.refresh_group_list()
            self.wake_status_var.set(f"分组已重命名为: {new_name}")

    def add_to_group(self):
        """添加客户端到分组"""
        if not self.current_group:
            messagebox.showinfo("提示", "请先选择一个分组")
            return
            
        selections = self.all_clients_listbox.curselection()
        if not selections:
            messagebox.showinfo("提示", "请选择要添加的客户端")
            return
            
        added_count = 0
        for index in selections:
            client_text = self.all_clients_listbox.get(index)
            # 从显示文本中提取IP地址
            parts = client_text.split(' - ')
            if len(parts) >= 2:
                ip = parts[1]
                if ip in self.clients:
                    client_info = self.clients[ip].copy()
                    # 检查是否已存在
                    if not any(c.get('ip') == ip for c in self.wake_groups[self.current_group]):
                        self.wake_groups[self.current_group].append(client_info)
                        added_count += 1
        
        if added_count > 0:
            self.save_wake_groups()
            self.refresh_group_clients()
            self.refresh_group_list()
            self.wake_status_var.set(f"已添加 {added_count} 个客户端到分组")

    def remove_from_group(self):
        """从分组移除客户端"""
        if not self.current_group:
            messagebox.showinfo("提示", "请先选择一个分组")
            return
            
        selections = self.group_clients_listbox.curselection()
        if not selections:
            messagebox.showinfo("提示", "请选择要移除的客户端")
            return
            
        # 从后往前删除，避免索引变化
        for index in sorted(selections, reverse=True):
            if index < len(self.wake_groups[self.current_group]):
                removed_client = self.wake_groups[self.current_group].pop(index)
                self.log_message(f"从分组 {self.current_group} 移除客户端: {removed_client.get('hostname', '未知')}", "INFO")
        
        self.save_wake_groups()
        self.refresh_group_clients()
        self.refresh_group_list()
        self.wake_status_var.set(f"已移除 {len(selections)} 个客户端")

    def wake_selected_clients(self):
        """唤醒选中的客户端"""
        if not self.current_group:
            messagebox.showinfo("提示", "请先选择一个分组")
            return
            
        selections = self.group_clients_listbox.curselection()
        if not selections:
            messagebox.showinfo("提示", "请选择要唤醒的客户端")
            return
            
        clients_to_wake = []
        for index in selections:
            if index < len(self.wake_groups[self.current_group]):
                client = self.wake_groups[self.current_group][index]
                clients_to_wake.append(client)
        
        if clients_to_wake:
            self.do_wake_clients(clients_to_wake, f"分组 '{self.current_group}' 中的选中客户端")

    def wake_entire_group(self):
        """唤醒整个分组"""
        if not self.current_group:
            messagebox.showinfo("提示", "请先选择一个分组")
            return
            
        clients_to_wake = self.wake_groups[self.current_group]
        if not clients_to_wake:
            messagebox.showinfo("提示", "当前分组中没有客户端")
            return
            
        self.do_wake_clients(clients_to_wake, f"分组 '{self.current_group}'")

    def do_wake_clients(self, clients, description):
        """执行唤醒操作"""
        success_count = 0
        total_count = len(clients)
        
        self.wake_status_var.set(f"开始唤醒 {description}...")
        
        def wake_thread():
            nonlocal success_count
            
            for client in clients:
                try:
                    mac_address = client.get('mac', '')
                    hostname = client.get('hostname', '未知')
                    ip = client.get('ip', '未知')
                    
                    # 检查MAC地址格式
                    if not mac_address or mac_address == '未知' or not self.is_valid_mac(mac_address):
                        self.log_message(f"客户端 {hostname} MAC地址无效: {mac_address}", "ERROR")
                        continue
                    
                    # 发送网络唤醒魔法包
                    if self.send_wake_on_lan_magic_packet(mac_address):
                        success_count += 1
                        self.log_message(f"成功发送唤醒包到: {hostname} ({ip}) - MAC: {mac_address}", "INFO")
                    else:
                        self.log_message(f"发送唤醒包到 {hostname} 失败", "ERROR")
                        
                except Exception as e:
                    self.log_message(f"唤醒客户端 {client.get('hostname', '未知')} 异常: {str(e)}", "ERROR")
            
            # 更新状态
            failed_count = total_count - success_count
            status_message = f"唤醒完成: 成功 {success_count}, 失败 {failed_count}"
            self.wake_status_var.set(status_message)
            
            # 显示结果
            if success_count > 0:
                messagebox.showinfo("唤醒完成", 
                    f"{description}唤醒完成:\n成功: {success_count}台\n失败: {failed_count}台")
            else:
                messagebox.showwarning("唤醒完成", 
                    f"{description}唤醒完成:\n成功: {success_count}台\n失败: {failed_count}台\n\n请检查MAC地址格式和网络连接。")
        
        # 在新线程中执行唤醒
        threading.Thread(target=wake_thread, daemon=True).start()

    def send_wake_on_lan_magic_packet(self, mac_address):
        """发送Wake-on-LAN魔法包"""
        try:
            # 清理MAC地址格式
            mac_address = mac_address.replace('-', ':').replace(' ', '')
            
            # 检查MAC地址格式
            if len(mac_address) != 17:
                return False
                
            # 创建魔法包
            mac_bytes = bytes.fromhex(mac_address.replace(':', ''))
            
            # 魔法包格式: 6xFF + 16xMAC地址
            magic_packet = b'\xff' * 6 + mac_bytes * 16
            
            # 创建UDP socket
            sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
            
            # 发送到广播地址
            sock.sendto(magic_packet, ('255.255.255.255', 9))
            sock.close()
            
            return True
            
        except Exception as e:
            self.log_message(f"发送Wake-on-LAN魔法包失败: {str(e)}", "ERROR")
            return False

    def is_valid_mac(self, mac):
        """验证MAC地址格式"""
        # 支持常见的MAC地址格式：00:11:22:33:44:55 或 00-11-22-33-44-55
        mac_pattern = re.compile(r'^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$')
        return bool(mac_pattern.match(mac))

    def distribute_files(self):
        """分发文件 - 增强版：添加文件大小检查和进度显示"""
        if not self.selected_clients:
            messagebox.showinfo("提示", "请先选择客户端")
            return
            
        files = filedialog.askopenfilenames(title="选择要分发的文件")
        if not files:
            return
            
        # 检查文件大小
        total_size = 0
        for file_path in files:
            try:
                total_size += os.path.getsize(file_path)
            except:
                pass
        
        # 警告大文件
        if total_size > 50 * 1024 * 1024:  # 50MB
            if not messagebox.askyesno("确认", f"总文件大小: {total_size/1024/1024:.1f}MB\n文件较大，分发可能需要较长时间，是否继续？"):
                return
        
        total_files = len(files)
        total_clients = len(self.selected_clients)
        success_count = 0
        total_operations = total_files * total_clients
        current_operation = 0
        
        # 创建进度窗口
        progress_window = tk.Toplevel(self.root)
        progress_window.title("文件分发进度")
        progress_window.geometry("400x150")
        progress_window.transient(self.root)
        
        ttk.Label(progress_window, text="文件分发进度:").pack(pady=10)
        
        progress_var = tk.DoubleVar()
        progress_bar = ttk.Progressbar(progress_window, variable=progress_var, maximum=100)
        progress_bar.pack(fill=tk.X, padx=20, pady=5)
        
        status_var = tk.StringVar(value="准备开始...")
        status_label = ttk.Label(progress_window, textvariable=status_var)
        status_label.pack(pady=5)
        
        def update_progress():
            progress = (current_operation / total_operations) * 100
            progress_var.set(progress)
            status_var.set(f"进度: {current_operation}/{total_operations}")
            progress_window.update()
        
        def do_distribute():
            nonlocal success_count, current_operation
            
            for file_index, file_path in enumerate(files):
                try:
                    filename = os.path.basename(file_path)
                    file_size = os.path.getsize(file_path)
                    
                    # 读取文件内容
                    with open(file_path, 'rb') as f:
                        file_content = f.read()
                    
                    # 编码为base64
                    file_content_b64 = base64.b64encode(file_content).decode('utf-8')
                    
                    for client_index, client in enumerate(self.selected_clients):
                        current_operation = file_index * total_clients + client_index + 1
                        progress_window.after(0, update_progress)
                        
                        self.log_message(f"分发文件 {filename} ({file_size/1024:.1f}KB) 到 {client['hostname']}", "INFO")
                        
                        # 发送文件到客户端
                        response = self.send_remote_command(
                            client['ip'],
                            'distribute_file',
                            filename=filename,
                            file_content=file_content_b64,
                            target_path="",  # 使用客户端默认路径
                            file_size=file_size
                        )
                        
                        if response and response.get('status') == 'success':
                            success_count += 1
                            saved_path = response.get('saved_path', '未知路径')
                            self.log_message(f"文件 {filename} 成功发送到 {client['hostname']} -> {saved_path}", "INFO")
                        else:
                            error_msg = response.get('message', '未知错误') if response else '无响应'
                            self.log_message(f"文件 {filename} 发送到 {client['hostname']} 失败: {error_msg}", "ERROR")
                            
                except Exception as e:
                    self.log_message(f"读取或发送文件 {file_path} 失败: {str(e)}", "ERROR")
            
            # 完成
            failed_count = total_operations - success_count
            progress_window.destroy()
            self.show_info_message("操作完成", 
                f"文件分发完成:\n成功: {success_count}\n失败: {failed_count}")
        
        # 在新线程中执行分发
        threading.Thread(target=do_distribute, daemon=True).start()
                    
    def execute_commands(self):
        """执行远程命令 - 修复计数显示"""
        if not self.selected_clients:
            messagebox.showinfo("提示", "请先选择客户端")
            return
            
        command = simpledialog.askstring("执行命令", "请输入要执行的命令:")
        if command:
            success_count = 0
            total_count = len(self.selected_clients)
            
            for client in self.selected_clients:
                response = self.send_remote_command(
                    client['ip'],
                    'execute_command',
                    command=command
                )
                
                if response and response.get('status') == 'success':
                    success_count += 1
                    self.log_message(f"成功在 {client['hostname']} 上执行命令: {command}", "INFO")
                else:
                    error_msg = response.get('message', '未知错误') if response else '无响应'
                    self.log_message(f"在 {client['hostname']} 上执行命令失败: {error_msg}", "ERROR")
            
            failed_count = total_count - success_count
            self.show_info_message("操作完成", 
                f"命令执行完成:\n成功: {success_count}\n失败: {failed_count}")

    def is_valid_ip(self, ip):
        """验证IP地址格式"""
        try:
            parts = ip.split('.')
            if len(parts) != 4:
                return False
            for part in parts:
                if not part.isdigit() or not 0 <= int(part) <= 255:
                    return False
            return True
        except:
            return False

    def show_info_message(self, title, message):
        """显示信息消息"""
        messagebox.showinfo(title, message)
        
    def show_error_message(self, message):
        """显示错误消息"""
        messagebox.showerror("错误", message)

    def run(self):
        """运行服务器"""
        self.root.mainloop()

if __name__ == "__main__":
    # 检查必要的依赖
    try:
        import ipaddress
    except ImportError:
        print("请安装所需依赖: pip install ipaddress")
        exit(1)
        
    server = FogIPServer()
    server.run()
